package com.example.ephuizi.stl.util.multi_light;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;

import com.example.ephuizi.stl.R;
import com.example.ephuizi.stl.util.STLFile;
import com.example.ephuizi.stl.util.STLUtil;
import com.example.ephuizi.stl.util.ShaderHelper;
import com.example.ephuizi.stl.util.photo_3d.ImageConvert3;
import com.example.ephuizi.stl.util.string_3d.DrawStringParam;
import com.example.ephuizi.stl.util.string_3d.StringConvert3D;
import com.example.ephuizi.stl.util.string_3d.Data3D;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ephuizi@gmail.com on 2015/8/26.
 */
public class GLObjLightForDraw {
    private static final String TAG = GLObjLightForDraw.class.getSimpleName();

    private final int POSITION_DATA_SIZE = 3;
    private final int NORMAL_DATA_SIZE = 3;
    private final int BYTES_PER_FLOAT = 4;

    private STLFile stlFile;
    private float mZoom;

    private int mProgram;
    private int _projectionSlot;
    private int _modelViewSlot;
    private int _normalMatrixSlot;
    private int _lightPositionSlot;
    private int _ambientSlot;
    private int _specularSlot;
    private int _shininessSlot;
    private int _diffuseSlot;
    private int _positionSlot;
    private int _normalSlot;

    private int _centerSlot;

    //
    /**
     * Used to hold a light centered on the origin in model space. We need a 4th coordinate so we can get translations to work when
     * we multiply this by our transformation matrices.
     */
    private final float[] mLightPosInModelSpace = new float[]{0.0f, 0.0f, 0.0f, -1.0f};//basic
    /**
     * Used to hold the current position of the light in world space (after transformation via model matrix).
     */
    private final float[] mLightPosInWorldSpace = new float[4];
    /**
     * Used to hold the transformed position of the light in eye space (after transformation via modelview matrix)
     */
    private final float[] mLightPosInEyeSpace = new float[4];

    /**
     * Stores a copy of the model matrix specifically for the light position.
     */
    private float[] mLightModelMatrix = new float[16];

    /**
     * Store the model matrix. This matrix is used to move models from object space (where each model can be thought
     * of being located at the center of the universe) to world space.
     */
    private float[] mModelMatrix = new float[16];//basic
    /**
     * Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
     * it positions things relative to our eye.
     */
    private float[] mViewMatrix = new float[16];//basic
    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];//basic
    /**
     * Store the accumulated rotation.
     */
    private final float[] mAccumulatedRotation = new float[16];

    /**
     * Store the current rotation.
     */
    private final float[] mCurrentRotation = new float[16];//basic

    private final float[] _normalMatrix = new float[16];

    /**
     * A temporary matrix.
     */
    private float[] mTemporaryMatrix = new float[16];

    private float[] _ambient = new float[4];
    private float[] _specular = new float[4];
    private float[] _diffuse = new float[4];
    private float _shininess;

    //controller
    private float mDeltaX = 0.0f;
    private float mDeltaY = 0.0f;

    private final float[] mColor = {0.0902f, 0.3451f, 0.611764f, 1.0f};

    private String stlPath;

    public GLObjLightForDraw(Context mv, String stlPath) {
        long t_begin = System.currentTimeMillis();

        this.stlPath = stlPath;

        //初始化顶点坐标与着色数据
        initVertexData();
        Log.v(TAG, "initVertexData");
        //初始化shader
        intShader(mv);
        Log.v(TAG, "intShader");
        initModelValue();

        ShaderHelper.checkGlError("GLObjLightForDraw");

        STLUtil.logUseTime(TAG, "GLObjLightForDraw", t_begin);
    }

    //初始化顶点坐标与着色数据的方法
    public void initVertexData() {
        if (stlPath.endsWith("stl") || stlPath.endsWith("STL")) {
            stlFile = STLUtil.parse(stlPath);
        } else if (stlPath.length() < 4) {
            List<DrawStringParam> params = new ArrayList<>();
            params.add(new DrawStringParam("A", Color.BLACK, Typeface.DEFAULT, 1f));
            params.add(new DrawStringParam("B", Color.BLACK, Typeface.DEFAULT, 0.5f));
            Data3D word3D = StringConvert3D.convert3DFullBottom(params);
            StringConvert3D.multiWord3DUsbHead(word3D);
            stlFile = StringConvert3D.toStlFile(word3D);
        } else {
            stlFile = new ImageConvert3().toStlFile(stlPath);
        }
    }

    //初始化shader
    public void intShader(Context mv) {
        //加载顶点着色器的脚本内容
        String vertexShader = ShaderHelper.loadSource(mv, R.raw.vertex_shader_light);
        //加载片元着色器的脚本内容
        String fragmentShader = ShaderHelper.loadSource(mv, R.raw.fragment_shader_light);

        final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
        final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);

        //基于顶点着色器与片元着色器创建程序
        mProgram = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
                new String[]{"vPosition", "aNormal", "vDiffuseMaterial"});

        ShaderHelper.checkGlError("createAndLinkProgram");

    }

    private void initModelValue() {
        //zoom
        // set model scale to suitable screen
        float m = stlFile.absMax();
        this.setmZoom(1.0f / m);

        // Initialize the accumulated rotation matrix
        Matrix.setIdentityM(mAccumulatedRotation, 0);

        // Position the eye in front of the origin.
        final float eyeX = 0.0f;
        final float eyeY = 0.0f;
        final float eyeZ = 2.0f;

        // We are looking toward the distance
        final float lookX = 0.0f;
        final float lookY = 0.0f;
        final float lookZ = 0.0f;

        // Set our up vector. This is where our head would be pointing were we holding the camera.
        final float upX = 0.0f;
        final float upY = 1.0f;
        final float upZ = 0.0f;

        // Set the view matrix. This matrix can be said to represent the camera position.
        // NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
        // view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
        Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
    }

    public void draw() {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        this.getSlotsFromProgram();

        this.updateSurface();


    }

    //
    // Get the attribute and uniform slot from program
    //
    private void getSlotsFromProgram() {
        // Set our per-vertex lighting program.
        GLES20.glUseProgram(mProgram);
        _projectionSlot = GLES20.glGetUniformLocation(mProgram, "projection");
        _modelViewSlot = GLES20.glGetUniformLocation(mProgram, "modelView");
        _normalMatrixSlot = GLES20.glGetUniformLocation(mProgram, "normalMatrix");
        _lightPositionSlot = GLES20.glGetUniformLocation(mProgram, "vLightPosition");
        _ambientSlot = GLES20.glGetUniformLocation(mProgram, "vAmbientMaterial");
        _specularSlot = GLES20.glGetUniformLocation(mProgram, "vSpecularMaterial");
        _shininessSlot = GLES20.glGetUniformLocation(mProgram, "shininess");
        _centerSlot = GLES20.glGetUniformLocation(mProgram, "aCenter");


        _positionSlot = GLES20.glGetAttribLocation(mProgram, "vPosition");
        _normalSlot = GLES20.glGetAttribLocation(mProgram, "aNormal");
        _diffuseSlot = GLES20.glGetAttribLocation(mProgram, "vDiffuseMaterial");

        ShaderHelper.checkGlError("getSlotsFromProgram");
    }

    /**
     * 设置颜色
     */
    private void setupLightsValue() {
        // Set up some default material parameters.
        _diffuse[0] = mColor[0];
        _diffuse[1] = mColor[1];
        _diffuse[2] = mColor[2];
        _diffuse[3] = 1.0f;

        _ambient[0] = mColor[0];
        _ambient[1] = mColor[1];
        _ambient[2] = mColor[2];
        _ambient[3] = 1.0f;

        _specular[0] = _specular[1] = _specular[2] = 0.7f;
        _specular[3] = 1.0f;

        _shininess = 10;
    }

    /**
     *
     */
    private void updateLights() {

        this.setupLightsValue();

        // Calculate position of the light. Push into the distance.
        Matrix.setIdentityM(mLightModelMatrix, 0);
        Matrix.translateM(mLightModelMatrix, 0, 0.0f, 0.0f, 0.5f);

        Matrix.multiplyMV(mLightPosInWorldSpace, 0, mLightModelMatrix, 0, mLightPosInModelSpace, 0);
        Matrix.multiplyMV(mLightPosInEyeSpace, 0, mViewMatrix, 0, mLightPosInWorldSpace, 0);
        // Pass in the light position in eye space.
        GLES20.glUniform3f(_lightPositionSlot, mLightPosInEyeSpace[0], mLightPosInEyeSpace[1], mLightPosInEyeSpace[2]);
        ShaderHelper.checkGlError("_lightPositionSlot");

        GLES20.glUniform4f(_ambientSlot, _ambient[0], _ambient[1], _ambient[2], _ambient[3]);
        GLES20.glUniform4f(_specularSlot, _specular[0], _specular[1], _specular[2], _specular[3]);
        GLES20.glVertexAttrib4f(_diffuseSlot, _diffuse[0], _diffuse[1], _diffuse[2], _diffuse[3]);
        GLES20.glUniform1f(_shininessSlot, _shininess);

        GLES20.glUniform3f(_centerSlot, stlFile.centerX(), stlFile.centerY(), stlFile.centerZ());
        ShaderHelper.checkGlError("_shininessSlot");
    }

    private void updateSurface() {
        // Translate the cube into the screen.
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.scaleM(mModelMatrix, 0, getmZoom(), getmZoom(), getmZoom());

        // Set a matrix that contains the current rotation.
        Matrix.setIdentityM(mCurrentRotation, 0);
        Matrix.rotateM(mCurrentRotation, 0, mDeltaX, 0.0f, 1.0f, 0.0f);
        Matrix.rotateM(mCurrentRotation, 0, mDeltaY, 1.0f, 0.0f, 0.0f);
        mDeltaX = 0.0f;
        mDeltaY = 0.0f;

        // Multiply the current rotation by the accumulated rotation, and then set the accumulated rotation to the result.
        Matrix.multiplyMM(mTemporaryMatrix, 0, mCurrentRotation, 0, mAccumulatedRotation, 0);
        System.arraycopy(mTemporaryMatrix, 0, mAccumulatedRotation, 0, 16);

        // Rotate the cube taking the overall rotation into account.
        Matrix.multiplyMM(mTemporaryMatrix, 0, mModelMatrix, 0, mAccumulatedRotation, 0);
        System.arraycopy(mTemporaryMatrix, 0, mModelMatrix, 0, 16);//update mModelMatrix

        // TODO drag

        // This multiplies the view matrix by the model matrix, and stores
        // the result in the MVM matrix
        // (which currently contains model * view).
        Matrix.multiplyMM(mMVMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);

        // Pass in the modelview matrix.
        GLES20.glUniformMatrix4fv(_modelViewSlot, 1, false, mMVMatrix, 0);//u_MVMatrix
        ShaderHelper.checkGlError("_modelViewSlot");
        // Load projection matrix
        GLES20.glUniformMatrix4fv(_projectionSlot, 1, false, mProjectionMatrix, 0);//u_MVMatrix
        ShaderHelper.checkGlError("_projectionSlot");


        //法线模型变换矩阵 = 模型视图矩阵的逆转置矩阵
        Matrix.invertM(mTemporaryMatrix, 0, mMVMatrix, 0);
        Matrix.transposeM(_normalMatrix, 0, mTemporaryMatrix, 0);
        GLES20.glUniformMatrix3fv(_normalMatrixSlot, 1, false, _normalMatrix, 0);

        //将顶点位置数据
        GLES20.glEnableVertexAttribArray(_positionSlot);
        GLES20.glVertexAttribPointer
                (
                        _positionSlot,
                        POSITION_DATA_SIZE,
                        GLES20.GL_FLOAT,
                        false,
                        POSITION_DATA_SIZE * BYTES_PER_FLOAT,
                        stlFile.getVertexBuffer()
                );
        ShaderHelper.checkGlError("_positionSlot");

        GLES20.glEnableVertexAttribArray(_normalSlot);
        GLES20.glVertexAttribPointer
                (
                        _normalSlot,
                        NORMAL_DATA_SIZE,
                        GLES20.GL_FLOAT,
                        false,
                        NORMAL_DATA_SIZE * BYTES_PER_FLOAT,
                        stlFile.getNormalBuffer()
                );
        ShaderHelper.checkGlError("_normalSlot");

        this.updateLights();

        //绘制加载的物体
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, stlFile.getFacetNum() * 3);
        GLES20.glDisableVertexAttribArray(_positionSlot);
        GLES20.glDisableVertexAttribArray(_normalSlot);

    }

    /**
     * 设置投影矩阵
     *
     * @param width  screen width
     * @param height screen height
     */
    public void setupProjection(int width, int height) {
        Matrix.setIdentityM(mProjectionMatrix, 0);
        // Generate a perspective matrix
        //
        final float ratio = (float) width / height;
        final float left = -ratio;
        final float right = ratio;
        final float bottom = -1.0f;
        final float top = 1.0f;
        final float near = 1.0f;
        final float far = 1000.0f;

        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);

        ShaderHelper.checkGlError("setupProjection");
    }

    public float getmZoom() {
        return mZoom;
    }

    public void setmZoom(float mZoom) {
        this.mZoom = mZoom;
    }

    public float getmDeltaX() {
        return mDeltaX;
    }

    public void setmDeltaX(float mDeltaX) {
        this.mDeltaX = mDeltaX;
    }

    public float getmDeltaY() {
        return mDeltaY;
    }

    public void setmDeltaY(float mDeltaY) {
        this.mDeltaY = mDeltaY;
    }

    public boolean setModelColor(int r, int g, int b) {
        if (0 <= r && r <= 255 && 0 <= b && b <= 255 && 0 <= g && g <= 255) {
            this.mColor[0] = r / 255;
            this.mColor[1] = g / 255;
            this.mColor[2] = b / 255;
        }
        return false;
    }
}
